package com.yueshop.seckill.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.yueshop.common.mq.SeckillOrderTo;
import com.yueshop.common.utils.R;
import com.yueshop.common.vo.MemberRespVo;
import com.yueshop.seckill.constant.SeckillConstant;
import com.yueshop.seckill.feign.CouponFeignService;
import com.yueshop.seckill.feign.ProductFeignService;
import com.yueshop.seckill.interceptor.LoingUserInterceptor;
import com.yueshop.seckill.service.SeckillService;
import com.yueshop.seckill.vo.SeckillSessionsWithSkusVo;
import com.yueshop.seckill.vo.SeckillSkuRedisTo;
import com.yueshop.seckill.vo.SkuinfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RabbitTemplate rabbitTemplate;


    @Override
    public void upSeckillSkuLatest3Days() {
        R r = couponFeignService.latest3DaysSession();
        //获取三天内秒杀数据
        List<SeckillSessionsWithSkusVo>  sessionData = r.getData(new TypeReference<List<SeckillSessionsWithSkusVo>>() {});
        //放入缓存 减轻数据库压力
        if(r.getCode()==0 && sessionData!=null){
            //放入redis
            //1.缓存秒杀活动信息
            saveSessionInfos(sessionData);
            //2.缓存秒杀活动关联的商品信息
            saveSessionSkuInfos(sessionData);
        }

    }

    public List<SeckillSkuRedisTo> blockHandler( BlockException ex){
        log.info("getcurrentSeckillSkusResource被限流了.......");
        return null;
    }



    //获取当前时间需要参加秒杀活动的商品
    @SentinelResource(value = "getcurrentSeckillSkusResource",blockHandler ="blockHandler" )
    @Override
    public List<SeckillSkuRedisTo> getcurrentSeckillSkus() {
        //1.获取当前时间是哪个场次
        long now = new Date().getTime();
        try (Entry entry = SphU.entry("SeckillSkus")) {
            //获取所有场次的key
            Set<String> keys = redisTemplate.keys(SeckillConstant.SECKILL_SESSION_CACHE_PREFIX + "*");
            for (String key : keys) {
                String[] time = key.replace(SeckillConstant.SECKILL_SESSION_CACHE_PREFIX, "").split("_");
                long startTime = Long.parseLong(time[0]);
                long endTime = Long.parseLong(time[1]);
                if (now >= startTime && now <= endTime) {
                    //2. 获取当前场次 有哪些秒杀商品
                    //获取当前场次及其对应的所有的商品skuid
                    List<String> range = redisTemplate.opsForList().range(key, 0, -1);

                    //获取绑定redis hash操作的对象
                    BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS_CACHE_PREFIX);
                    //获取当前场次的所有商品信息
                    List<String> seckillSkus = ops.multiGet(range);
                    if (seckillSkus != null && seckillSkus.size() > 0) {
                        List<SeckillSkuRedisTo> collect = seckillSkus.stream().map(sku -> {
                            return JSON.parseObject(sku, SeckillSkuRedisTo.class);
                        }).collect(Collectors.toList());
                        return collect;
                    }
                    break;
                }
            }
        }catch (BlockException ex){
            log.info("资源被限流了{}"+ex.getMessage());
        }
        return null;
    }

    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        //获取所有需要参与秒杀的商品key
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS_CACHE_PREFIX);

        Set<String> keys = ops.keys();
        if(keys!=null&&keys.size()>0){
            //使用正则表达式  数字_skuid
            String regx ="\\d_"+skuId;
            for (String key : keys) {
                if(Pattern.matches(regx,key)){
                    //如果匹配成功, 证明有该商品的秒杀活动
                    String skuInfoStr = ops.get(key);
                    SeckillSkuRedisTo skuRedisTo = JSON.parseObject(skuInfoStr, SeckillSkuRedisTo.class);
                    //分为2中情况 1正在被秒杀  2 还没有开始秒杀
                    long now = new Date().getTime();
                    long startTime = skuRedisTo.getStartTime();
                    long endTime = skuRedisTo.getEndTime();

                    if(now<startTime||now >endTime){
                        //不在活动范围内 随机码不能暴露
                        skuRedisTo.setRandomCode(null);
                    }
                    return skuRedisTo;
                }
            }
        }
        return null;
    }

    @Override
    public String seckill(String killId, String key, Integer num)  {

        long l = System.currentTimeMillis();
        MemberRespVo memberRespVo = LoingUserInterceptor.loingUser.get();


        //.1获取当前秒杀商品的详细信息
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS_CACHE_PREFIX);

        String skuInfoStr = ops.get(killId);
        if(!StringUtils.isEmpty(skuInfoStr)){
            //2.校验合法性
            SeckillSkuRedisTo skuRedisTo = JSON.parseObject(skuInfoStr, SeckillSkuRedisTo.class);
            //2.1 校验时间的合法性
            long startTime = skuRedisTo.getStartTime();
            long endTime = skuRedisTo.getEndTime();
            long now = new Date().getTime();
            if(now>= startTime && now<=endTime){
                String killId2 = skuRedisTo.getPromotionSessionId()+"_"+skuRedisTo.getSkuId();
                String randomCode = skuRedisTo.getRandomCode();

                //2.2 校验随机码和商品id是否匹配   killId : 场次id_商品id
                if(killId2.equals(killId)&&key.equals(randomCode)){
                    //2.3 校验秒杀的商品数量
                    if(num<=Integer.parseInt(skuRedisTo.getSeckillLimit().toString())){
                        //2.4 校验用户是否已经买过了  幂等性处理  userId_场次id_商品id
                        String isBuyKey =memberRespVo.getId()+"_"+killId;
                        Boolean isNotBuy = redisTemplate.opsForValue().setIfAbsent(isBuyKey, num.toString(), endTime - now, TimeUnit.MILLISECONDS);
                        if(isNotBuy){
                            //站位成功 没被购买过
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SECKILL_SKU_STOCK_SEMAPHORE + randomCode);
                            boolean b = semaphore.tryAcquire(num);
                            if(b){
                                //获取信号量 秒杀成功 快速下单 发消息给mq
                                String orderSn  = IdWorker.getTimeId();
                                //封装数据 发送消息给MQ
                                SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                seckillOrderTo.setOrderSn(orderSn);
                                seckillOrderTo.setMemberId(memberRespVo.getId());
                                seckillOrderTo.setPromotionSessionId(skuRedisTo.getPromotionSessionId());
                                seckillOrderTo.setNum(num);
                                seckillOrderTo.setSkuId(skuRedisTo.getSkuId());
                                seckillOrderTo.setSeckillPrice(skuRedisTo.getSeckillPrice());

                                rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillOrderTo);
                                long l1 = System.currentTimeMillis();
                                log.info("秒杀耗时时间....."+(l1-l)+"毫秒");
                                return orderSn;
                            }

                        }
                    }
                }

            }



        }


        return null;
    }

    //缓存秒杀活动关联的商品信息
    private void saveSessionSkuInfos(List<SeckillSessionsWithSkusVo> sessionData) {

        sessionData.stream().forEach(session->{
            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SeckillConstant.SECKILL_SKUS_CACHE_PREFIX);
            session.getSeckillSkus().stream().forEach(sku->{
                String key =sku.getPromotionSessionId()+"_"+sku.getSkuId();
                //如果当前场次秒杀商品没有上架 再进行上架
                if(!ops.hasKey(key)){
                    SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                    //1.获取sku的基本信息
                    R r = productFeignService.info(sku.getSkuId());
                    if(r.getCode()==0){
                        SkuinfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuinfoVo>() {
                        });
                        redisTo.setSkuinfoVo(skuInfo);
                    }
                    //2.sku秒杀信息
                    BeanUtils.copyProperties(sku,redisTo);
                    //3.秒杀时间信息
                    redisTo.setStartTime(session.getStartTime().getTime());
                    redisTo.setEndTime(session.getEndTime().getTime());
                    //4.设置秒杀随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    redisTo.setRandomCode(token);
                    //5.秒杀的商品库存 使用分布式信号量
                    RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SECKILL_SKU_STOCK_SEMAPHORE + token);
                    //设置商品秒杀的信号量
                    semaphore.trySetPermits(Integer.parseInt(sku.getSeckillCount().toString()));
                    ops.put(key, JSON.toJSONString(redisTo));
                }
            });
        });


    }

    //缓存秒杀活动信息
    private void saveSessionInfos(List<SeckillSessionsWithSkusVo> sessionData) {
        if (sessionData!=null){
            //时间转为long 方便比较
            sessionData.stream().forEach(session->{
                long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                //设置 key  活动开始时间+_+活动结束时间
                String key = SeckillConstant.SECKILL_SESSION_CACHE_PREFIX + startTime + "_" + endTime;
                //保证幂等性,
                Boolean hasKey = redisTemplate.hasKey(key);
                if(!hasKey){
                    // 所有的商品  场次id_商品id 作为values
                    List<String> value = session.getSeckillSkus().stream().map(item -> item.getPromotionSessionId().toString() + "_" + item.getSkuId().toString()
                    ).collect(Collectors.toList());
                    redisTemplate.opsForList().leftPushAll(key,value);
                }
            });
        }

    }
}
